import {fromPairs, isObject} from "lodash-unified"
import {PropType} from "vue";

export const epPropKey = '__epPropKey'
// 判断参数 T 是否是 never 类型
export type IfNever<T, Y = true, N = false> = [T] extends [never] ? Y : N
// 依据 Default 是否为 never 来判断是否包含 default 字段
export type EpProp<Type, Default, Required> = {
    readonly type: PropType<Type>,
    readonly required: [Required] extends [true] ? true : false
    readonly validator: ((val: unknown) => boolean) | unknown
    [epPropKey]: true
} & IfNever<Default, unknown, { readonly default: Default }>
// 判断是否可以 prop 化
export const isEpProp = (val: unknown): val is EpProp<any, any, any> =>
    isObject(val) && !!(val as any)[epPropKey];

function hasOwn(prop: any, defaultValue: any): boolean {
    return Object.prototype.hasOwnProperty.call(prop, defaultValue) && prop[defaultValue] === defaultValue;
}


const buildProp = (prop: any, key?: string) => {
    // 1. 检查 prop, prop 不是对象 或者 如果是对象需要判断该对象是否可以被 prop 化
    if (!isObject(prop) || !isEpProp(prop)) return prop as any

    // 2. 提取参数
    const {
        values,
        required,
        default: defaultValue,
        type,
        validator
    } = prop

    const _validator =
        values || validator
            ? (val: unknown) => {
                let valid = false
                let allowedValues: unknown[] = []


                if(values){
                    allowedValues = Array.from(values)
                    if(hasOwn(prop, 'default')){
                        allowedValues.push(defaultValue)
                    }
                    // 如果 valid 为 false，就以后面的表达式来赋值
                    valid ||= allowedValues.includes(val)
                }
                if(validator) valid ||= validator(val)

                if(!valid && allowedValues.length > 0){
                    const allowValuesText: string = [
                        ...new Set(allowedValues)
                    ].map((value) => JSON.stringify(value)).join(",")
                    console.warn(
                        `Invalid prop: validation failed${
                        key ?` for prop "${key}"`: ''}.
                         Expected one of [${allowValuesText}], got value ${JSON.stringify(
                            val)}.`
                    )
                }
                return valid
            } : undefined


    const epProp: any = {
        type,
        required: !!required,
        validator: _validator,
        [epPropKey]: true
    }
    if(hasOwn(prop, 'default')) epProp.default = defaultValue
    return epProp
}


export const buildProps = (props: any) => {
    return fromPairs(
        Object.entries(props).map(([key, option]) => [
            key,
            buildProp(option as any, key),
        ])
    )
}
